package com.virtual.repository.plugins.cassandra;


import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

import com.datastax.driver.core.Cluster;
import com.datastax.driver.core.ColumnDefinitions.Definition;
import com.datastax.driver.core.ResultSet;
import com.datastax.driver.core.Row;
import com.datastax.driver.core.Session;
import com.virtual.repository.common.criteria.CriteriaBuilder;
import com.virtual.repository.common.queries.Query;
import com.virtual.repository.common.queries.QueryBuilder;
import com.virtual.repository.common.queries.ResultEntity;
import com.virtual.repository.plugins.api.IDatabasePlugin;

public class CassandraDBDriver implements IDatabasePlugin{

	private Cluster cluster;
	private Session session;

	private String login;
	private String password;
	private String node;



	public static void main(String[] args) {
		QueryBuilder qBuilder = new QueryBuilder(CassandraBeanTest.class);
		//qBuilder.addEntityName("testowy.beanTest");
		//		qBuilder.addProperty("name");
		//		qBuilder.addProperty("number");

		CriteriaBuilder cb = new CriteriaBuilder();

		//cb.addCriterion("number", CriteriaOperator.EQUALS, 1);

		try {
			qBuilder.addCriteria(cb.build());
		} catch (CloneNotSupportedException e) {
			
				throw new RuntimeException(e);
		}


		//qBuilder.addOrderByRule("number", true);

		qBuilder.setLimit(10);

		Query query = qBuilder.build(); 
		query.setEntityName("pz_namespace.complex");
		System.out.println(query.toString());

		CassandraDBDriver cassandraDBDriver = new CassandraDBDriver("aa", "bb", "127.0.0.1");
		try {
			cassandraDBDriver.connect();
		} catch (UnknownHostException e) {
			throw new RuntimeException(e);
		}
		ArrayList<ResultEntity> entities=(ArrayList<ResultEntity>) cassandraDBDriver.executeQuery(query);
		System.out.println(entities);
		System.out.println(cassandraDBDriver.executeQueryForSingleResult(query));
		System.out.println(cassandraDBDriver.executeQueryForCount(query));
		cassandraDBDriver.disconnect();

	}

	public CassandraDBDriver() {
		//needed for initialization in AbstractRepositoryManager
	}

	public CassandraDBDriver(String login, String password, String node) {
		super();
		this.login = login;
		this.password = password;
		this.node = node;
	}

	@Override
	public void connect() throws UnknownHostException {

		cluster=Cluster.builder().addContactPoint(node).build();
		session=cluster.connect();
	}

	@Override
	public void disconnect() {
		if (!session.isClosed()) {
			session.close();
		}
		if (!cluster.isClosed()) {
			cluster.close();
		}
	}

	@Override
	public Collection<ResultEntity> executeQuery(Query query) {

		ResultSet results = session.execute( buildSelect(query,false) );

		ArrayList<ResultEntity> entities = new ArrayList<>();
		try {
			for(Row row: results) {

				ResultEntity entity;

				entity = parseRow( row);

				entities.add(entity);
			}
		} 
		catch (InstantiationException | IllegalAccessException
				| NoSuchFieldException | SecurityException e) {
			throw new RuntimeException(e);
		}
		return entities;
	}


	/** Gets ResultEntity object from com.datastax.driver.core.Row object
	 * @param  row to be parsed
	 * @return entity parsed from row
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 * @throws NoSuchFieldException
	 * @throws SecurityException
	 */
	private ResultEntity parseRow(Row row) 
			throws  InstantiationException, IllegalAccessException, NoSuchFieldException, SecurityException{

		ResultEntity result = new ResultEntity();

		Iterator<Definition> iterator =row.getColumnDefinitions().iterator();
		while( iterator.hasNext()) {
			Definition definition=iterator.next();
			Object byteValues =definition.getType().deserialize(row.getBytesUnsafe(definition.getName()));
			result.setField(definition.getName(),byteValues);
		}
		return result;
	}

	@Override
	public ResultEntity executeQueryForSingleResult(Query query) {
		ResultSet results = session.execute( buildSelect(query,false) );
		ResultEntity entity = null;


		try {
			entity=parseRow(results.one());
		} catch (InstantiationException | IllegalAccessException
				| NoSuchFieldException | SecurityException e) {
			throw new RuntimeException(e);
		}
		return entity;
	}

	@Override
	public long executeQueryForCount(Query query) {
		ResultSet results = session.execute( buildSelect(query,true) );
		long count = 0;
		count = results.one().getLong(0);
		return count;
	}

	/**Builds SELECT statement for given query.
	 * 
	 * @param query Selection query to execute
	 * @param count If true then SELECT statement will have form SELECT COUNT (*) FROM ...
	 * @return
	 */
	private String buildSelect(Query query, boolean count) {
		StringBuffer buffer = new StringBuffer();
		buffer.append("SELECT ");
		if (count) {
			buffer.append(" COUNT (*) ");
		}
		else {
			if (query.getSelectedFields() != null && !query.getSelectedFields().isEmpty()) {
				for(int i = 0; i < query.getSelectedFields().size(); i++) {
					if (i !=0) {
						buffer.append(", ");
					}
					buffer.append(query.getSelectedFields().get(i));
				}
			}
			else {
				buffer.append("*");
			}
		}


		buffer.append(" FROM ").append(query.getEntityName());

		if(query.getSelectionCriteria() != null && !query.getSelectionCriteria().isEmpty()) {
			buffer.append(" WHERE ");

			for (int i = 0; i<query.getSelectionCriteria().size() ; i++) {
				buffer.append(query.getSelectionCriteria().get(i));
			}
		}
		if ( query.getLimit() != null) {
			buffer.append(" LIMIT ").append(query.getLimit());
		}

		buffer.append(";");
		return buffer.toString();
	}

	public String getLogin() {
		return login;
	}

	public void setLogin(String login) {
		this.login = login;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public String getNode() {
		return node;
	}

	public void setNode(String node) {
		this.node = node;

	}

}
